// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: node.cpp 209 2008-08-28 10:05:39Z heavyzheng $
//

#ifndef ZZHENG_XML_SOURCE
#define ZZHENG_XML_SOURCE
#endif // ZZHENG_XML_SOURCE

#include <zzheng/xml/dom/libxml2/node.hpp>
#include <zzheng/xml/dom/libxml2/document.hpp>
#include <zzheng/xml/dom/libxml2/utility.hpp>
#include <zzheng/xml/dom/exception.hpp>

#include <libxml/tree.h>
#include <cassert>
#include <string>


namespace zzheng {
namespace xml {
namespace dom {
namespace libxml2_ {

    node::node(xmlNode* px): raw_(px) {
        assert(raw_ != 0 && "Underlying libxml2 node should not be null.");
    }

    node::~node() {
        raw_ = 0;
    }

    node_t node::type() const {
        return to_node_t(raw()->type);
    }

    std::string node::content() const {
        // Quoted from libxml2's documentation:
        //   Read the value of a node, this can be either the text carried directly by this node if
        //   it's a TEXT node or the aggregate string of the values carried by this node child's
        //   (TEXT and ENTITY_REF). Entity references are substituted.
        // Note: the returned xmlChar* (maybe null) should be freed (using xmlFree) by the caller.
        std::string value;
        xmlChar* raw_value = xmlNodeGetContent( const_cast<xmlNode*>(raw()) );
        if (raw_value != 0) {
            value = to_chars(raw_value);
            xmlFree(raw_value);
            raw_value = 0;
        }
        return value;
    }

    void node::set_content(const std::string& content) {
        // Quoted from libxml2 documentation:
        //   (xmlNodeSetContent) Replace the content of a node. NOTE: @content is supposed to be a
        //   piece of XML CDATA, so it allows entity references, but XML special chars need to be
        //   escaped first by using xmlEncodeEntitiesReentrant() resp. xmlEncodeSpecialChars().
        // Note: if this node is an element, after this function is called, all its original child
        // nodes will be destroyed.
        xmlNodeSetContent(raw(), to_xml_chars(content.c_str()));
    }

    bool node::is_orphan() const {
        // Note: in libxml2, the root element's parent node is the owner document.
        return (raw()->parent == 0);
    }

    std::string node::name() const {
        switch (raw()->type) {
        case XML_ELEMENT_NODE:
        case XML_ATTRIBUTE_NODE:
            return to_chars(raw()->name);
        case XML_NAMESPACE_DECL:
            return to_chars(raw()->name);
        case XML_TEXT_NODE:
        case XML_CDATA_SECTION_NODE:
            return "#text";
        case XML_COMMENT_NODE:
            return "#comment";
        default:
            assert(! "Unknown libxml2 node type");
            return "#unknown";
        }
    }

    std::string node::path() const {
        std::string path;
        xmlChar* raw_path = xmlGetNodePath( const_cast<xmlNode*>(raw()) );
        if (raw_path != 0) {
            path = to_chars(raw_path);
            xmlFree(raw_path);
            raw_path = 0;
        }
        return path;
    }

    std::string node::str() const {
        std::string tmp;
        str(tmp);
        return tmp;
    }

    void node::str(std::string& str) const {
        xmlBuffer* buffer = xmlBufferCreate();
        xmlNodeDump(buffer, raw()->doc, const_cast<xmlNode*>(raw()), 0, 0);
        str = to_chars(buffer->content);
        xmlBufferFree(buffer);
    }        

    document& node::doc() {
        return const_cast<document&>( (static_cast<const node&>(*this)).doc() );
    }

    const document& node::doc() const {
        assert(raw()->doc != 0 && raw()->doc->_private != 0);
        const document* doc = static_cast<const document*>(raw()->doc->_private);
        return (*doc);
    }

    basic_node_ptr<element> node::parent() {
        return basic_node_ptr<element>( const_cast<element*>(parent_()) );
    }

    basic_node_ptr<const element> node::parent() const {
        return basic_node_ptr<const element>( parent_() );
    }

    const element* node::parent_() const {
        if (raw()->parent != 0 && raw()->parent->type == XML_ELEMENT_NODE) {
            return static_cast<const element*>(raw()->parent->_private);
        } else {
            return 0;
        }
    }

    xmlNode* node::clone_raw(bool recursive) const {
        // The third parameter (extends) of xmlDocCopyNode takes the value 1 or 2:
        // - 1 to do a recursive copy (attributes, namespaces and children when applicable);
        // - 2 to copy attributes and namespaces (when applicable).
        xmlNode* px = xmlDocCopyNode( const_cast<xmlNode*>(raw()),
                                      raw()->doc,
                                      (recursive ? 1 : 2) );
        // Check the cloned node.
        if (px == 0) {
            throw internal_dom_error("Fail to clone node: xmlDocCopyNode() returns null.");
        } else if (px->type != raw()->type) {
            throw internal_dom_error("Fail to clone node: the cloned node has a wrong node type.");
        } else if (px->doc != raw()->doc) {
            throw internal_dom_error("Fail to clone node: the cloned node has a wrong document.");
        } else if (px->_private == 0) {
            throw internal_dom_error("Fail to clone node: the cloned node hasn't _private.");
        }
        // TODO: Add the cloned libxml2 node object to the owner document's orphan list.
        // doc().add_orphan_(static_cast<node*>(px->_private));
        // Return a pointer to the cloned node.
        return px;
    }

} // namespace zzheng::xml::dom::libxml2_
} // namespace zzheng::xml::dom
} // namespace zzheng::xml
} // namespace zzheng





